package com.pubinfo.passbook.common.utils.financial;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.util.encoders.Hex;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;

public class SignUtil {
  /**
   * ECC k1 pab
   */
  private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
  /**
   * ECC k1 pab
   */
  private static final ECDomainParameters CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH());
  /**
   * ECC k1 n
   */
  private static final BigInteger HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1);
  
  /**
   * 用bouncyCastle进行数据签名
   *
   * @param dataContent 待签名数据载体(instCode+payload+timestamp+nonce).bytes
   * @return 签名结果
   * @throws IOException
   * @throws PKCSException
   * @throws OperatorCreationException
   */
  @SuppressWarnings({"resource", "JavaDoc"})
  private static byte[] sign1(String privateKey1, String privatePass, byte[] dataContent) throws IOException, PKCSException,
      OperatorCreationException {
    // 签名工具init
    Security.addProvider(new BouncyCastleProvider());
    InputStream privkeyInputStream1 = new ByteArrayInputStream(privateKey1.getBytes(StandardCharsets.UTF_8));
    PEMParser parser = new PEMParser(new InputStreamReader(privkeyInputStream1));
    PKCS8EncryptedPrivateKeyInfo pair = (PKCS8EncryptedPrivateKeyInfo) parser.readObject();
    JceOpenSSLPKCS8DecryptorProviderBuilder jce = new JceOpenSSLPKCS8DecryptorProviderBuilder();
    jce.setProvider("BC");
    InputDecryptorProvider decProv = jce.build(privatePass == null ? null : privatePass.toCharArray());
    PrivateKeyInfo info = pair.decryptPrivateKeyInfo(decProv);
    JcaPEMKeyConverter pemKeyConverter = new JcaPEMKeyConverter();
    PrivateKey privateKey = pemKeyConverter.getPrivateKey(info);
    BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) privateKey;
    BigInteger d = bcecPrivateKey.getD();
    byte[] privateKeyId = toBytesPadded(d, 32);
    ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
    ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(new BigInteger(1, privateKeyId), CURVE);
    signer.init(true, privKey);
    // 获取摘要
    byte[] digest = null;
    try {
      MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
      digest = messageDigest.digest(dataContent);
    } catch (NoSuchAlgorithmException ignored) {
    }
    BigInteger[] components = signer.generateSignature(digest);
    if (!(components[1].compareTo(HALF_CURVE_ORDER) <= 0)) {
      BigInteger bi = components[1];
      components[1] = CURVE.getN().subtract(bi);
    }
    int recId = -1;
    // 1 header + 32 bytes for R + 32 bytes for S
    byte v = (byte) recId;
    byte[] r = toBytesPadded(components[0], 32);
    byte[] s = toBytesPadded(components[1], 32);
    byte[] sig = new byte[65];
    System.arraycopy(r, 0, sig, 0, 32);
    System.arraycopy(s, 0, sig, 32, 32);
    sig[64] = v;
    return sig;
  }
  
  @SuppressWarnings("SameParameterValue")
  private static byte[] toBytesPadded(BigInteger value, int length) {
    byte[] result = new byte[length];
    byte[] bytes = value.toByteArray();
    int bytesLength;
    int srcOffset;
    if (bytes[0] == 0) {
      bytesLength = bytes.length - 1;
      srcOffset = 1;
    } else {
      bytesLength = bytes.length;
      srcOffset = 0;
    }
    if (bytesLength > length) {
      throw new RuntimeException("Input is too large to put in byte array of size " + length);
    }
    System.arraycopy(bytes, srcOffset, result, length - bytesLength, bytesLength);
    return result;
  }
  
  public static String sign(String privateKey1, String privatePass, String dataContent) {
    try {
      byte[] a = dataContent.getBytes(StandardCharsets.UTF_8);
      MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
      messageDigest.update(a);
      byte[] c = sign1(privateKey1, privatePass, messageDigest.digest());
      return Hex.toHexString(c);
    } catch (OperatorCreationException | PKCSException | IOException | NoSuchAlgorithmException e) {
      e.printStackTrace();
      return null;
    }
  }
  
}
